package uestc.lj.gulishop.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import uestc.lj.common.to.SkuEsModel;
import uestc.lj.common.utils.R;
import uestc.lj.gulishop.search.config.GulishopElasticSearchConfig;
import uestc.lj.gulishop.search.constant.EsConstant;
import uestc.lj.gulishop.search.feign.ProductFeignService;
import uestc.lj.gulishop.search.service.MallSearchService;
import uestc.lj.gulishop.search.vo.AttrResponseVO;
import uestc.lj.gulishop.search.vo.BrandVO;
import uestc.lj.gulishop.search.vo.SearchParamVO;
import uestc.lj.gulishop.search.vo.SearchResponseVO;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author crazlee
 */
@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private ProductFeignService productFeignService;

    /**
     * 根据检索结果返回页面
     *
     * @param searchParamVO 检索的所有参数
     * @return 返回检索的所有结果
     */
    @Override
    public SearchResponseVO search(SearchParamVO searchParamVO) {
        SearchResponseVO searchResponseVO = null;
        // 1.动态构建出查询需要的dsl语句
        SearchRequest searchRequest = buildSearchRequest(searchParamVO);
        try {
            // 2. 执行检索请求
            SearchResponse response = restHighLevelClient.search(searchRequest, GulishopElasticSearchConfig.COMMON_OPTIONS);
            // 3. 分析响应数据并封装bean
            searchResponseVO = buildSearchResponse(response, searchParamVO);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return searchResponseVO;
    }

    private SearchRequest buildSearchRequest(SearchParamVO searchParamVO) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 构建bool--query
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 1.模糊匹配--must
        if (!StringUtils.isEmpty(searchParamVO.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParamVO.getKeyword()));
            // 5.高亮
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("<b style='color:red'>");
            builder.postTags("</b>");
            sourceBuilder.highlighter(builder);
        }
        // 2.排序--filter
        // 2.1 三级分类id
        if (searchParamVO.getCatalog3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParamVO.getCatalog3Id()));
        }
        // 2.2 品牌id
        if (searchParamVO.getBrandId() != null && searchParamVO.getBrandId().size() > 0) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", searchParamVO.getBrandId()));
        }
        // 2.3 属性
        if (searchParamVO.getAttrs() != null && searchParamVO.getAttrs().size() > 0) {

            for (String attr : searchParamVO.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                // attr = 1_5存:7存
                String[] s = attr.split("_");
                // 检索的属性id
                String attrId = s[0];
                // 检索属性的值
                String[] attrValues = s[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));

                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQueryBuilder.filter(nestedQuery);
            }
        }
        // 2.4 是否拥有库存
        if (searchParamVO.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("hasStock", searchParamVO.getHasStock() == 1));
        }
        // 2.5 价格区间
        if (!StringUtils.isEmpty(searchParamVO.getSkuPrice())) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] price = searchParamVO.getSkuPrice().split("_");
            if (price[0] != null && price[1] != null) {
                rangeQuery.gte(price[0]).lte(price[1]);
            } else if (price[0] == null && price[1] != null) {
                rangeQuery.gte(0).lte(price[1]);
            } else {
                rangeQuery.gte(price[0]);
            }
            boolQueryBuilder.filter(rangeQuery);
        }

        sourceBuilder.query(boolQueryBuilder);

        // 3.排序
        if (!StringUtils.isEmpty(searchParamVO.getSort())) {
            String sort = searchParamVO.getSort();
            // sort=hotScore_asc/desc
            String[] sortInfo = sort.split("_");
            sourceBuilder.sort(sortInfo[0], "asc".equalsIgnoreCase(sortInfo[1]) ? SortOrder.ASC : SortOrder.DESC);
        }
        // 4.分页
        int index = (searchParamVO.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE;
        sourceBuilder.from(index);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        // 6.聚合分析
        // 6.1 品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(50);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg"));
        sourceBuilder.aggregation(brandAgg);
        // 6.2 分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(10);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        sourceBuilder.aggregation(catalogAgg);
        // 6.3 属性聚合
        NestedAggregationBuilder nestedAttrAgg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        nestedAttrAgg.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(nestedAttrAgg);

        return new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
    }

    private SearchResponseVO buildSearchResponse(SearchResponse response, SearchParamVO searchParamVO) {
        SearchResponseVO searchResponseVO = new SearchResponseVO();
        // 1.返回查询到的商品
        SearchHits searchHits = response.getHits();

        List<SkuEsModel> products = new ArrayList<>();
        if (searchHits.getHits() != null && searchHits.getHits().length > 0) {
            for (SearchHit hit : searchHits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                if (!StringUtils.isEmpty(searchParamVO.getKeyword())) {
                    HighlightField skuTitleHighlight = hit.getHighlightFields().get("skuTitle");
                    skuEsModel.setSkuTitle(skuTitleHighlight.getFragments()[0].string());
                }
                products.add(skuEsModel);
            }
        }
        searchResponseVO.setProducts(products);
        // 2.当前所有商品涉及到的所有属性信息
        List<SearchResponseVO.AttrResponseVO> attrResponseVOS = new ArrayList<>();
        ParsedNested attrAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResponseVO.AttrResponseVO attrResponseVO = new SearchResponseVO.AttrResponseVO();
            // 属性的id
            attrResponseVO.setAttrId(bucket.getKeyAsNumber().longValue());
            // 属性的名字
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrResponseVO.setAttrName(attrName);
            // 属性的值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attrValueAgg.getBuckets().stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
            attrResponseVO.setAttrValue(attrValues);
            attrResponseVOS.add(attrResponseVO);
        }

        searchResponseVO.setAttrs(attrResponseVOS);

        // 3.当前所有商品涉及到的所有品牌信息
        List<SearchResponseVO.BrandResponseVO> brandResponseVOS = new ArrayList<>();
        ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResponseVO.BrandResponseVO brandResponseVO = new SearchResponseVO.BrandResponseVO();
            // 品牌id
            brandResponseVO.setBrandId(bucket.getKeyAsNumber().longValue());
            // 品牌名字
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandResponseVO.setBrandName(brandName);
            // 品牌的图片
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            brandResponseVO.setBrandImg(brandImg);

            brandResponseVOS.add(brandResponseVO);
        }
        searchResponseVO.setBrands(brandResponseVOS);
        // 4.当前所有商品涉及到的所有分类信息
        ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
        List<SearchResponseVO.CatalogResponseVO> catalogResponseVOS = new ArrayList<>();
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResponseVO.CatalogResponseVO catalogResponseVO = new SearchResponseVO.CatalogResponseVO();
            // 分类id
            catalogResponseVO.setCatalogId(Long.parseLong(bucket.getKeyAsString()));
            // 分类名
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogResponseVO.setCatalogName(catalogName);

            catalogResponseVOS.add(catalogResponseVO);
        }
        searchResponseVO.setCatalogs(catalogResponseVOS);
        // 5.分页的当前页码
        searchResponseVO.setPageNum(searchParamVO.getPageNum());
        // 6.分页的总记录数
        long total = searchHits.getTotalHits().value;
        searchResponseVO.setTotal(total);
        // 7.分页的总页码
        int totalPage = total % EsConstant.PRODUCT_PAGESIZE == 0 ? (int) total / EsConstant.PRODUCT_PAGESIZE : (int) (total / EsConstant.PRODUCT_PAGESIZE + 1);
        searchResponseVO.setTotalPages(totalPage);
        // 8.分页的导航页码
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPage; i++) {
            pageNavs.add(i);
        }
        searchResponseVO.setPageNavs(pageNavs);

        // 9.构建属性面包屑数据
        if (searchParamVO.getAttrs() != null && searchParamVO.getAttrs().size() > 0) {
            List<SearchResponseVO.NavVO> navVOList = searchParamVO.getAttrs().stream().map(attr -> {
                SearchResponseVO.NavVO navVO = new SearchResponseVO.NavVO();
                // 1.分析每个attrs传过来的查询参数值
                String[] s = attr.split("_");
                navVO.setNavValue(s[1]);
                // 根据attrId查询出attr名字
                R r = productFeignService.info(Long.valueOf(s[0]));
                if (r.getCode() == 0) {
                    AttrResponseVO attrResponseVO = r.getData("attr", new TypeReference<AttrResponseVO>() {
                    });
                    navVO.setNavName(attrResponseVO.getAttrName());
                } else {
                    // 没有就将attrId设置上
                    navVO.setNavName(s[0]);
                }
                // 2.取消面包屑之后，要跳转到那个地方，将请求地址的url里面的当前置空

                String replace = replaceQueryString(searchParamVO, attr, "attrs");
                navVO.setLink("http://search.gulishop.com/list.html?" + replace);
                return navVO;
            }).collect(Collectors.toList());

            searchResponseVO.setNavs(navVOList);
        }

        // 10.品牌的面包屑导航
        if (searchParamVO.getBrandId() != null && searchParamVO.getBrandId().size() > 0) {
            List<SearchResponseVO.NavVO> navs = searchResponseVO.getNavs();
            SearchResponseVO.NavVO navVO = new SearchResponseVO.NavVO();
            navVO.setNavName("品牌");
            // 查询所有品牌
            R r = productFeignService.getBrands(searchParamVO.getBrandId());
            if (r.getCode() == 0) {
                List<BrandVO> brandVOS = r.getData("data", new TypeReference<List<BrandVO>>() {
                });
                StringBuffer stringBuffer = new StringBuffer();
                String replace = "";
                for (BrandVO brandVO : brandVOS) {
                    stringBuffer.append(brandVO.getBrandName() + ";");
                    replace = replaceQueryString(searchParamVO, brandVO.getBrandId() + "", "brandId");

                }
                navVO.setNavValue(stringBuffer.toString());
                navVO.setLink("http://search.gulishop.com/list.html?" + replace);
            }
            navs.add(navVO);

        }

        return searchResponseVO;
    }

    private String replaceQueryString(SearchParamVO searchParamVO, String value, String key) {
        String encode = null;
        try {
            encode = URLEncoder.encode(value, "UTF-8");
            // 当前端属性中有空格时要特殊处理
            encode = encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        String replace = searchParamVO.get_queryString().replace("&" + key + "=" + encode, "");
        return replace;
    }
}
